package com.ccsoft.thread.service;

import lombok.extern.slf4j.Slf4j;

/**
 * 2021年12月16日 08:20:46 chanchaw
 * 同步模式之保护性暂停
 */
@Slf4j
public class No9GuardedSuspension {
    public static void main(String[] args) {
        GuardedSuspension gs = new GuardedSuspension();
        new Thread(() -> {
            log.info("开始获取结果");
            Object result = gs.get(2000);
            log.info("开始打印结果");
            String content = result != null ? result.toString() : "未能获取到结果";
            log.info("获取到的结果是：" + content);
        },"threadGet").start();

        new Thread(() -> {
            log.info("创建了设置结果的线程");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            gs.set(new Object());
            log.info("结果设置完毕");
        },"threadSet").start();
    }


    /**
     * 2021年12月16日 09:46:58 chanchaw
     * 在视频教程的基础上追加：调用 get 时传入小于等于0的数字则表示不设置过期时间 - 无限期等待数据的到来
     */
    static class GuardedSuspension {
        private Object result;

        /**
         * 获取结果 result 如果不存在则死循环一直试图获取。
         * 传入参数 timeout 表示睡眠一定时间后自动再次获取结果并返回
         * @param timeout 等待的时间，单位：毫秒
         * @return 返回成员变量 result
         */
        public synchronized Object get(long timeout){
            long begin = System.currentTimeMillis();
            long passedTime = 0;
            while( result == null ) {
                long surplusTime = timeout - passedTime;
                // 在超时时间内可无限获取结果，一旦达到超时时间则退出循环，不再获取数据
                // 此时即使结果是 NULL，也会返回 NULL
                if( surplusTime <= 0 && timeout > 0 ) break;
                try {
                    if( timeout <=0 )
                        this.wait();
                    else
                        this.wait(surplusTime);// 出现假唤醒时只要等待剩余的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 当其他线程使用空数据进行假唤醒时，上一次已经等待1秒（假设传入参数 timeout 为2秒 - 线程检测到没有数据每次等待两秒）
                // 那么继续下次等待时只要再等待剩余的一秒，所以上面 this.wait 里的参数是 surplusTime - 剩余需要等待的时间
                // 如果 this.wait 里面直接使用传入参数 timeout ，那么出现假唤醒时就要全额等待时间
                passedTime = System.currentTimeMillis() - begin;
            }
            return result;
        }

        public synchronized void set(Object obj){
            this.result = obj;
            this.notifyAll();// 设置结果后唤醒所有线程
        }
    }
}
